package com.wthink.hzgj.service.jt808

import com.wthink.hzgj.util.hexStringToByteArray
import java.nio.ByteBuffer
import kotlin.experimental.and
import kotlin.experimental.or
import kotlin.experimental.xor

class JT808Protocol {
    var messageType: Short = 0 // required
    var messageProps: Short = 0 // required
    var simNo: String = "" // required
    var messageSerialNo: Short = 0 // optional
    var totalPackets: Short = 0 // optional
    var packetNo: Short = 0 // optional
    var messageBody: ByteArray? = null
        set(messageBody) {
            field = messageBody
            this.messageBodyLength = (messageBody?.size ?: 0).toShort()
        } // optional

    /**
     * 是否分包消息中的一个子包
     *
     * @return
     */
    var isSubPacket: Boolean
        get() = messageProps and 0x2000 == 0x2000.toShort()
        set(`val`) {
            var messageProps = this.messageProps
            if (`val`) {
                messageProps = messageProps or 0x2000
            } else {
                messageProps = messageProps and 0xdfff.toShort()
            }
            this.messageProps = messageProps
        }

    /**
     * 消息体长度
     *
     * @return
     */
    var messageBodyLength: Short
        get() = (messageProps and 0x03ff).toShort()
        private set(length) {
            var messageProps = this.messageProps
            this.messageProps = (messageProps and 0xfc00.toShort() or (length and 0x03ff)).toShort()
        }

    /////////////////////////////////////////////////////////////////////
    @Throws(Exception::class)
    fun readFromBytes(bytes: ByteArray) {
        val validBytes = unescape(bytes)
        val actual = getChecksum(validBytes, 1, validBytes.size - 2)
        val expected = validBytes[validBytes.size - 2]
        if (actual != expected) {
            throw Exception(
                String.format(
                    "checksum mismatch,expected:%#04x,actual:%#04x",
                    expected,
                    actual
                )
            )
        }
        val buffer = ByteBuffer.wrap(validBytes, 1, validBytes.size - 3)
        this.messageType = buffer.short
        this.messageProps = buffer.short
        val simBytes = ByteArray(6)
        buffer.get(simBytes)
        val builder = StringBuilder()
        for (b in simBytes) {
            builder.append(String.format("%02x", b))
        }
        this.simNo = builder.substring(1).toString()
        this.messageSerialNo = buffer.short
        if (isSubPacket) {
            this.totalPackets = buffer.short
            this.packetNo = buffer.short
        }
        val bodyLength = messageBodyLength
        if (bodyLength > 0) {
            var messageBody = ByteArray(bodyLength.toInt())
            buffer.get(messageBody)
            this.messageBody = messageBody
        }
        if (buffer.hasRemaining()) {
            throw Exception("invalid packet length")
        }
    }

    fun writeToBytes(): ByteArray {
        val headerLength = (if (isSubPacket) 16 else 12).toShort()
        val messageBody = messageBody
        val bodyLength = (messageBody?.size ?: 0).toShort()
        val buffer = ByteBuffer.allocate(headerLength + bodyLength)
        buffer.putShort(messageType)
        buffer.putShort(messageProps)
        var simNo = "000000000000" + this.simNo!!
        simNo = simNo.substring(simNo.length - 12)
        buffer.put(simNo.hexStringToByteArray())

        buffer.putShort(messageSerialNo)
        if (isSubPacket) {
            buffer.putShort(totalPackets)
            buffer.putShort(packetNo)
        }
        if (messageBody != null) {
            buffer.put(messageBody)
        }
        val bytes = buffer.array()
        val checksum = getChecksum(bytes, 0, bytes.size)
        val validBytes = ByteArray(bytes.size + 1)
        System.arraycopy(bytes, 0, validBytes, 0, bytes.size)
        validBytes[validBytes.size - 1] = checksum
        val msgBytes = escape(validBytes)
        val result = ByteArray(msgBytes.size + 2)
        result[result.size - 1] = 0x7e
        result[0] = result[result.size - 1]
        System.arraycopy(msgBytes, 0, result, 1, msgBytes.size)
        return result
    }

    /**
     * jt808协议反转义0x7d0x01->0x7d,0x7d0x02->0x7e
     *
     * @param bytes
     * @return
     */
    private fun unescape(bytes: ByteArray): ByteArray {
        val buffer = ByteBuffer.allocate(bytes.size)
        var i = 0
        while (i < bytes.size) {
            if (bytes[i].toInt() == 0x7d) {
                if (bytes[i + 1].toInt() == 0x01) {
                    buffer.put(0x7d.toByte())
                    i++
                } else if (bytes[i + 1].toInt() == 0x02) {
                    buffer.put(0x7e.toByte())
                    i++
                }
            } else {
                buffer.put(bytes[i])
            }
            i++
        }
        buffer.flip()
        val result = ByteArray(buffer.limit())
        buffer.get(result)
        return result
    }

    private fun escape(bytes: ByteArray): ByteArray {
        val buffer = ByteBuffer.allocate(bytes.size * 2)// 转义时0x7e、0x7d变为2个字节，最坏情况长度变为2倍
        for (i in bytes.indices) {
            if (bytes[i].toInt() == 0x7d) {
                buffer.put(0x7d.toByte())
                buffer.put(0x01.toByte())
            } else if (bytes[i].toInt() == 0x7e) {
                buffer.put(0x7d.toByte())
                buffer.put(0x02.toByte())
            } else {
                buffer.put(bytes[i])
            }
        }
        buffer.flip()
        val result = ByteArray(buffer.limit())
        buffer.get(result)
        return result
    }

    private fun getChecksum(data: ByteArray, pos: Int, len: Int): Byte {
        var result: Byte = 0
        for (i in pos until len) {
            result = result xor data[i]
        }
        return result
    }
}
